package com.hanter.downloader.service;

import android.content.Context;
import android.net.Uri;
import android.util.Log;

import androidx.annotation.Nullable;

import com.hanter.downloader.DebugLog;
import com.hanter.downloader.Delivery;
import com.hanter.downloader.Downloader;
import com.hanter.downloader.Partial;
import com.hanter.downloader.Response;
import com.hanter.downloader.TaskInfo;
import com.hanter.downloader.exception.ResponseException;

import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Locale;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;

/**
 * 文件名：DownloadForkTask <br/>
 * 描述：分块下载任务
 * 创建时间：2022/02/07 10:22
 *
 * @author wangmingshuo
 * @version 1.0
 */
public class DownloadForkTask extends RecursiveAction {

    private final Context context;
    private final Downloader.Request request;
    private final Delivery delivery;
    private final DownloadService downloadService;
    @Nullable
    private final Partial partial;

    /**
     * 2 的 n 次方
     */
    public static final int MAX_FORK_COUNT = 4;
    public static final long THRESHOLD = 2 * 1024 * 1024;
    /**
     * 获取文件大小 -1L 表示未知，直接下载
     */
    public static final long UNKNOWN_LENGTH = -1L;

    public static final String TAG = DownloadForkTask.class.getSimpleName();

    private final int fork;

    public DownloadForkTask(Context context, Partial partial, Downloader.Request request,
                            DownloadService downloadService, Delivery delivery) {
        this(context, 0, partial, request, downloadService, delivery);
    }

    public DownloadForkTask(Context context, int fork, @Nullable Partial partial, Downloader.Request request,
                            DownloadService downloadService, Delivery delivery) {
        this.context = context;
        this.fork = fork;
        this.partial = partial;
        this.request = request;
        this.downloadService = downloadService;
        this.delivery = delivery;
    }

    public Downloader.Request getRequest() {
        return request;
    }

    @Override
    protected void compute() {
        checkCancelOrThrow();

        this.request.addTask(this);

        Partial part;
        if (this.partial == null) {
            // 获取下载文件大小
            TaskInfo taskInfo = downloadService.getTaskInfo(this.request.getUrl());
            if (taskInfo == null) {
                if (this.request.isCanceled()) {
                    throw new CancellationException();
                } else {
                    Throwable e = getException();
                    DebugLog.d(TAG, "failed");
                    throw new ResponseException(Response.ERROR_FAILED, "下载失败", e);
                }
            }

            if (taskInfo.getContentLength() > 0) {
                part = new Partial(0, taskInfo.getContentLength() - 1);
                request.contentLength = taskInfo.getContentLength();
            } else {
                part = new Partial(0, UNKNOWN_LENGTH);
                request.contentLength = UNKNOWN_LENGTH;
            }

            // 耗时操作后，检查是否取消
            checkCancelOrThrow();
        } else {
            part = this.partial;
        }

        if (part.getEnd() == UNKNOWN_LENGTH || part.getEnd() - part.getStart() <= THRESHOLD || fork > MAX_FORK_COUNT) {
            // 直接执行任务，不再拆分
            try {
                download(part);
            } catch (InterruptedIOException | InterruptedException e) {
                Log.d(TAG, "InterruptedException");
                e.printStackTrace();

                if (this.request.isCanceled()) {
                    throw new CancellationException();
                } else {
                    throw new RuntimeException(e);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else {
            // 进行任务拆分
            long middle = (part.getStart() + part.getEnd()) / 2;
            // 响应 失败处理
            DownloadForkTask taskl = new DownloadForkTask(context, this.fork + 1,
                    new Partial(part.getStart(), middle), request, downloadService, delivery);
            DownloadForkTask taskr = new DownloadForkTask(context, this.fork + 1,
                    new Partial(middle + 1, part.getEnd()), request, downloadService, delivery);
            ForkJoinTask.invokeAll(taskl, taskr);
        }
    }

    private void checkCancelOrThrow() {
        if (this.request.isCanceled()) {
            DebugLog.d(TAG, "checkCancelOrThrow, request is canceled.");
            throw new CancellationException();
        }

        // if the request is failed.
        if (this.request.isFailed()) {
            DebugLog.d(TAG, "checkCancelOrThrow, request is failed.");
            throw new ResponseException(Response.ERROR_FAILED, "下载失败");
        }

        Throwable e = getException();
        if (e != null) {
            DebugLog.d(TAG, "checkCancelOrThrow, request has a exception.");

            DebugLog.exception(e);

            if (e instanceof CancellationException) {
                this.request.cancel();
                throw (CancellationException) e;
            } else if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            } else {
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        // 取消相应的方法堵塞方法
        return super.cancel(mayInterruptIfRunning);
    }

    private void download(Partial part) throws IOException, InterruptedException {
        // 执行下载任务 start end
        DebugLog.d(TAG,   String.format(Locale.US, "开始下载块，start:%10d, end:%10d",
                part.getStart() + part.getOffset(), part.getEnd()));

        OnProgressIncreaseListener listener = new OnProgressIncreaseListener() {
            @Override
            public void onIncrease(long delta) throws InterruptedException {
                long progress = request.incProgress(delta);
                delivery.postProgress(request, progress, request.contentLength);

                checkCancelOrThrow();

                request.checkPausedOrAwait();
            }
        };

        Uri uri = Uri.parse(request.getFilePath());
        if (uri.getScheme() == null) {
            downloadService.download(new File(request.getFilePath()), request.getUrl(),
                    part.getStart() + part.getOffset(), part.getEnd(), listener);
        } else {
            downloadService.download(context, uri, request.getUrl(),
                    part.getStart() + part.getOffset(), part.getEnd(), listener);
        }
    }

}
